package org.hit.burkun.mapping;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.hit.burkun.db.DBHelper;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class BasicOnlineMappingProcessor implements IOnlineMappingProcessor {
	public static Logger logger = LoggerFactory
			.getLogger(BasicOnlineMappingProcessor.class);

	/**
	 * 构造函数
	 * 
	 * @param retryTimes
	 *            超时重试次数
	 * @param useDbCache
	 *            是否使用数据库缓存
	 */
	public BasicOnlineMappingProcessor(int retryTimes, boolean useDbCache) {
		this.retryTimes = retryTimes;
		this.useDbCahche = useDbCache;
		init();
	}

	// public BasicOnlineMappingProcessor(int retryTimes){
	// this(retryTimes, true);
	// }
	//
	// public BasicOnlineMappingProcessor(){
	// this(3, true);
	// }
	/**
	 * @param fromKey
	 *            从那个数据库映射的
	 * @param toKey
	 *            映射到那个数据库的
	 * @resData 结果文件，是一个hashMap,对应的是id到ids的映射
	 */
	@Override
	public void process(DBKEY fromKey, DBKEY toKey,
			Map<String, Set<String>> resData) {
		if (isDataFromWeb) {
			// save to db
			logger.info("[START] save data to dbcache!");
			saveDbCache(fromKey, toKey, resData);
			logger.info("[END] save data to dbcahe!");
		}
	}

	@Override
	public void mapping(DBKEY fromKey, DBKEY toKey, Collection<String> data) {
		if (data.size() > 0) {
			if (useDbCahche) {
				Collection<String> noMapping = mappingFromDbCache(fromKey,
						toKey, data);
				if (noMapping.size() > 0) {
					mappingFromUniprot(fromKey, toKey, noMapping);
				}
			} else {
				mappingFromUniprot(fromKey, toKey, data);
			}
		} else {
			logger.warn("input data size is 0!!");
		}
	}

	private void mappingFromUniprot(DBKEY fromKey, DBKEY toKey,
			Collection<String> data) {
		logger.info("get result set from UniportKB webset!");
		isDataFromWeb = true;
		int curCount = 0;
		if (data.size() < maxCount) {
			UniprotOnlineMapping.runMapping(fromKey, toKey, data, this,
					retryTimes);
		} else {
			Collection<String> tempRes = new LinkedList<String>();
			for (String key : data) {
				curCount += 1;
				tempRes.add(key);
				if (curCount == maxCount) {
					curCount = 0;
					UniprotOnlineMapping.runMapping(fromKey, toKey, tempRes,
							this, retryTimes);
					try {
						// 防止频繁提交，服务器拒绝请求！
						Thread.sleep(sleepInterval);
					} catch (InterruptedException e) {
						logger.error("Error In UniportKBMapping...", e);
						e.printStackTrace();
					}
				}
			}
			if (curCount > 0) {
				curCount = 0;
				UniprotOnlineMapping.runMapping(fromKey, toKey, tempRes, this,
						retryTimes);
			}
		}
	}

	private void saveDbCache(DBKEY fromKey, DBKEY toKey,
			Map<String, Set<String>> resData) {
		dbHelper.connectionDB();
		Collection<String> res = assmbelInsertStr(fromKey, toKey, resData);
		for (String sql : res) {
			dbHelper.exec(sql);
		}
		dbHelper.disconnection();
	}

	private Collection<String> mappingFromDbCache(DBKEY fromKey, DBKEY toKey,
			Collection<String> data) {
		// 1. 若数据库中没有，那么把没有的提交到服务器
		// 2.
		// 若数据库中有，那么把它们交给process接口处理,通过isDataFromWeb判断该数据是否是从web上获取的，如果是，那么存入数据库
		// 3. 在数据库中，dbkey_type1 在字典序上始终小于dbkey_type2,这样可以保证没有冗余的出现
		// 4. 返回没有被mapping上去的
		isDataFromWeb = false;
		logger.info("get mapping result from dbcache!");
		// 组装mapping的sql;
		Collection<String> mappingStrs = assmbelQueryStr(fromKey, toKey, data);
		dbHelper.connectionDB();
		Map<String, Set<String>> resData = new Hashtable<String, Set<String>>();
		Set<String> allFromKey = new HashSet<String>(data);
		for (String str : mappingStrs) {
			Collection<String[]> dbres = dbHelper.queryData(str, 2);
			for (String[] res : dbres) {
				// from res[0], to res[1]
				// mark
				if (resData.containsKey(res[0])) {
					resData.get(res[0]).add(res[1]);
				} else {
					HashSet<String> set = new HashSet<String>();
					set.add(res[1]);
					resData.put(res[0], set);
				}
			}
		}
		// 去掉已经在数据库缓存里面的
		allFromKey.removeAll(resData.keySet());
		process(fromKey, toKey, resData);
		dbHelper.disconnection();
		return allFromKey;
	}

	private void init() {
		Date date = new Date();
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		Calendar calendar = Calendar.getInstance();// 日历对象
		calendar.setTime(date);// 设置当前日期
		calendar.add(Calendar.MONTH, -maxMonth);// 月份减一,这里可以定制时间差
		validDateTime = sdf.format(calendar.getTime());// 输出格式化的日期
	}

	private Collection<String> assmbelInsertStr(DBKEY from, DBKEY to,
			Map<String, Set<String>> resData) {
		String template = "('%s', '%s', '%s', '%s')";
		Collection<String> list = new LinkedList<>();
		if (from.getTag().compareTo(to.getTag()) > 0) {
			// from > to
			StringBuilder sb = new StringBuilder();
			int counter = 0;
			Set<Entry<String, Set<String>>> entries = resData.entrySet();
			for (Entry<String, Set<String>> entry : entries) {
				String key = entry.getKey();
				Set<String> values = entry.getValue();
				for (String value : values) {
					counter++;
					sb.append(String.format(template, value, key, to.getTag(),
							from.getTag()) + ",");
					if (counter == maxCount) {
						String sqlStr = insertStr
								+ sb.substring(0, sb.length() - 1);
						list.add(sqlStr);
						counter = 0;
						sb.setLength(0);
					}
				}
			}
			if (counter > 0) {
				String sqlStr = insertStr + sb.substring(0, sb.length() - 1);
				list.add(sqlStr);
			}
		} else {
			// from <= to
			StringBuilder sb = new StringBuilder();
			int counter = 0;
			Set<Entry<String, Set<String>>> entries = resData.entrySet();
			for (Entry<String, Set<String>> entry : entries) {
				String key = entry.getKey();
				Set<String> values = entry.getValue();
				for (String value : values) {
					counter++;
					sb.append(String.format(template, value, key, to.getTag(),
							from.getTag()) + ",");
					if (counter == maxCount) {
						String sqlStr = insertStr
								+ sb.substring(0, sb.length() - 1);
						list.add(sqlStr);
						counter = 0;
						sb.setLength(0);
					}
				}
			}
			if (counter > 0) {
				String sqlStr = insertStr + sb.substring(0, sb.length() - 1);
				list.add(sqlStr);
			}
		}
		return list;
	}

	private Collection<String> assmbelQueryStr(DBKEY from, DBKEY to,
			Collection<String> data) {
		Collection<String> list = new LinkedList<String>();
		int counter = 0;
		StringBuilder sb = new StringBuilder();
		if (from.getTag().compareTo(to.getTag()) > 0) {
			// from 大于 to，这样的话需要反过来做，组装成如下格式
			// select [dbkey2], [dbkey1] form dbkey_mapping where dbkey1_type =
			// [to] and dbkey2_type = [from] and add_data > %s and [dbkey2] in
			// (%s)
			// 每 maxCount个组成一个query字符串
			for (String key : data) {
				counter += 1;
				sb.append("'");
				sb.append(key);
				sb.append("',");
				if (counter == maxCount) {
					String dataStr = sb.substring(0, sb.length() - 1);
					String res = String.format(queryStr, "dbkey2", "dbkey1",
							to.getTag(), from.getTag(), validDateTime,
							"dbkey2", dataStr);
					list.add(res);
					sb.setLength(0);
					counter = 0;
				}
			}
			if (counter > 0) {
				String dataStr = sb.substring(0, sb.length() - 1);
				String res = String.format(queryStr, "dbkey2", "dbkey1",
						to.getTag(), from.getTag(), validDateTime, "dbkey2",
						dataStr);
				list.add(res);
				sb.setLength(0);
				counter = 0;
			}
		} else {
			// 当 from <= to时
			// select [dbkey1], [dbkey2] form dbkey_mapping where dbkey1_type =
			// [from] and dbkey2_type = [to] and add_data > %s and [dbkey1] in
			// (%s)
			for (String key : data) {
				counter += 1;
				sb.append("'");
				sb.append(key);
				sb.append("',");
				if (counter == maxCount) {
					String dataStr = sb.substring(0, sb.length() - 1);
					String res = String.format(queryStr, "dbkey1", "dbkey2",
							from.getTag(), to.getTag(), validDateTime,
							"dbkey1", dataStr);
					list.add(res);
					sb.setLength(0);
					counter = 0;
				}
			}
			if (counter > 0) {
				String dataStr = sb.substring(0, sb.length() - 1);
				String res = String.format(queryStr, "dbkey1", "dbkey2",
						from.getTag(), to.getTag(), validDateTime, "dbkey1",
						dataStr);
				list.add(res);
				sb.setLength(0);
				counter = 0;
			}
		}
		return list;
	}

	protected int sleepInterval = 1000;
	protected int maxCount = 500;
	protected int retryTimes = 3;
	protected boolean useDbCahche = true;
	private String validDateTime;
	private boolean isDataFromWeb = false;
	private int maxMonth = 12; // 设置一年后更新数据，这样我就毕业了
	// 如果数据库中不清除过期数据，后果就是数据库会越变越大
	private String queryStr = "select %s, %s from dbkey_mapping where dbkey1_type = '%s' and dbkey2_type = '%s' and add_date > '%s' and %s in (%s)";
	private String insertStr = "insert into dbkey_mapping (dbkey1, dbkey2, dbkey1_type, dbkey2_type) values";
	private DBHelper dbHelper = DBHelper.getLocalBioSearch();

}
